﻿using System;
using System.Data;
using System.Collections.Generic;
using System.Data.Common;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using Oracle.DataAccess.Client;
using System.Data.SqlClient;

namespace Petaframe.DataAccess
{

    /// <summary>
    /// Insert,Update,Delete işlemlerinin tamamının bir transaction altında tek seferde yapılmasını sağlar.
    /// <para>Örneğin iki defa insert, bir defa update metodunu çağırdıktan sonra Commit() metodunu çağırdığınızda, yaptığınız tüm işlemler
    /// toplu olarak tek seferde veritabanına gönderilir. Tüm işlemler transaction ile yapılır. Eğer gönderilen işlemlerden herhangi birtanesi hata verirse, 
    /// tüm işlemler geri alınır ve hiç biri çalıştırılmaz.</para>
    /// </summary>
    public class UnitOfWork : IUnitOfWork
    {
        private IDataService _dataService;        
        private ICommandBuilder _currentCommand;
        private ObservableCollection<ICommandBuilder> _observer;
     

        public UnitOfWork(IDataService dataService)
        {
            _dataService = dataService;            
            _observer = new ObservableCollection<ICommandBuilder>();
            _observer.CollectionChanged += _observer_CollectionChanged;
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }
        protected virtual void Dispose(bool disposing)
        {
            if (disposing)
            {
                _observer.Clear();
                _observer.CollectionChanged -= _observer_CollectionChanged;
                _observer = null;
                _currentCommand = null;

                if (_dataService != null)
                {
                    _dataService.Dispose();
                    _dataService = null;
                }
            }

        }
        ~UnitOfWork()
        {
            Dispose(false);
        }

        void _observer_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
        {
            if (e.Action == NotifyCollectionChangedAction.Add)
            {   
                _currentCommand = e.NewItems[0] as ICommandBuilder;
            }
        }

        /// <summary>
        /// Insert işlemi yapar
        /// </summary>
        /// <typeparam name="T">Eklenecek sınıf</typeparam>
        /// <param name="item">Eklenecek sınıf</param>
        public void Insert<T>(T item) 
        {
           
            if (item is string)
            {
                this.Insert(item.ToString(), CommandType.Text);
                return;
            }           

            ICommandBuilder cmd2 = new ObjectParameterCommand<T>(item,_dataService.Insert);

            _observer.Add(cmd2);
                     
        }

        /// <summary>
        /// Insert işlemi yapar
        /// </summary>
        /// <param name="commandText">Ekleme işlemi için kullanılacak komut.</param>
        /// <param name="commandType">Komut tipi.</param>
        public void Insert(string command, CommandType commandType=CommandType.Text)
        {
            
            if (string.IsNullOrWhiteSpace(command))
                throw new ArgumentNullException("commandText");

            IStringParameterCommandBuilder cmd = new StringParameterCommand(command, commandType, _dataService.Insert, _dataService);
           
            _observer.Add(cmd);         
        }
        /// <summary>
        /// Update işlemi yapar
        /// </summary>
        /// <typeparam name="T">Güncellenecek sınıf.</typeparam>
        /// <param name="item">Güncellenecek sınıf.</param>
        public void Update<T>(T item)
        {
            
            if (item is string)
            {
                Update(item.ToString(), CommandType.Text);
                return;
            }

            ICommandBuilder cmd = new ObjectParameterCommand<T>(item, _dataService.Update);
           
            _observer.Add(cmd);
        }
        /// <summary>
        /// Update işlemi yapar
        /// </summary>
        /// <param name="commandText">Güncelleme işlemi için kullanılacak komut.</param>
        /// <param name="commandType">Komut tipi.</param>
        public void Update(string command, CommandType commandType )
        {
            
            if (string.IsNullOrWhiteSpace(command))
                throw new ArgumentNullException("commandText");

            IStringParameterCommandBuilder cmd = new StringParameterCommand(command, commandType, _dataService.Update,_dataService);
            
            _observer.Add(cmd);
        }
        /// <summary>
        /// Delete işlemi yapar
        /// </summary>
        /// <typeparam name="T">Silincek sınıf</typeparam>
        /// <param name="item">Silinecek sınıf</param>
        public void Delete<T>(T item) 
        {
            
            if (item is string)
            {
                Delete(item.ToString(), CommandType.Text);
                return;
            }

            ICommandBuilder cmd = new ObjectParameterCommand<T>(item, _dataService.Delete);
            
            _observer.Add(cmd);
        }
        /// <summary>
        /// Delete işlemi yapar
        /// </summary>
        /// <param name="commandText">Silme işlemi için kullanılacak komut.</param>
        /// <param name="commandType">Komut tipi.</param>
        public void Delete(string command, CommandType commandType )
        {
           
            if (string.IsNullOrWhiteSpace(command))
                throw new ArgumentNullException("commandText");

            IStringParameterCommandBuilder cmd = new StringParameterCommand(command, commandType, _dataService.Delete, _dataService);
           
            _observer.Add(cmd);
        }

        /// <summary>
        /// Parametre olarak verilen DataTable nesnesi üzerinde yapılan tüm işlemleri veritabanına gönderir.
        /// </summary>
        /// <param name="item">Üzerinde işlem yapılacak olan nesne</param>
        public void PersistTable(DataTable item)
        {           
            ICommandBuilder cmd = new TableParameterCommand(item, _dataService.DetachedQuery.PersistTable);           
            _observer.Add(cmd);
        }

        /// <summary>
        /// Parametre olarak sql cümlesi alan metodlar için, veritabanı parametresi eklemek amacıyla kullanılır.
        /// </summary>
        /// <param name="parameterName">Parametre adı.</param>
        /// <param name="parameterValue">Parametre değeri.</param>
        /// <param name="dataType">Parametrenin sqlserver veritabanında ki tipi.</param>
        public void AddInputParameter(string parameterName, object parameterValue, DbType dbType)
        {
           
            if (!(_dataService is ISqlServerDataService))
                throw new InvalidOperationException("AddInputParameter metodunu yanlış kullandınız. Eğer Oracle için parametre eklemek istediyseniz, diğer aşırı yüklenmiş metodu kullanın.");

            var cmd = _currentCommand as IStringParameterCommandBuilder;

            if (cmd == null)
                throw new InvalidOperationException("Bu metodu sql komutu alan metodlarda kullanabilirsiniz. Örneğin: Delete(string command, CommandType commandType ) metodu ile kullanabilirsiniz.");

            cmd.Parameters.Add(new SqlParameter() {ParameterName=parameterName,Value=parameterValue,DbType=dbType });
        }

        /// <summary>
        /// Parametre olarak sql cümlesi alan metodlar için, veritabanı parametresi eklemek amacıyla kullanılır.
        /// </summary>
        /// <param name="parameterName">Parametre adı.</param>
        /// <param name="parameterValue">Parametre değeri.</param>
        /// <param name="dataType">Parametrenin Oracle veritabanında ki tipi.</param>
        public void AddInputParameter(string parameterName, object parameterValue, OracleDbType dbType)
        {                      

            if (!(_dataService is IOracleDataService))
                throw new InvalidOperationException("AddInputParameter metodunu yanlış kullandınız. Eğer SQLServer için parametre eklemek istediyseniz, diğer aşırı yüklenmiş metodu kullanın.");

            var cmd = _currentCommand as IStringParameterCommandBuilder;

            if (cmd == null)
                throw new InvalidOperationException("Bu metodu sql komutu alan metodlarda kullanabilirsiniz. Örneğin: Delete(string command, CommandType commandType ) metodu ile kullanabilirsiniz.");

            cmd.Parameters.Add(new OracleParameter() { ParameterName = parameterName, Value = parameterValue, OracleDbTypeEx = dbType });
        }

        /// <summary>
        /// Yapılan tüm işlemlerin veritabanına gönderilip bir transaction altında toplu olarak çalıştırılmasını sağlar.
        /// </summary>
        public void Commit()
        {
            _dataService.BeginTransaction();

            foreach (ICommandBuilder cmd in _observer)
            {
                cmd.Execute();
            }

            _dataService.CommitTransaction();
        }

        /// <summary>
        /// Veritabanına gönderilen işlemlerden herhangi biri hata verirse bu komut tüm işlemleri geri alır.
        /// </summary>
        public void RollBack()
        {
            _dataService.RollbackTransaction();
        }

       
    }
}
